/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2009, Around(J2)ME				                                 **
 **   All rights reserved.                                                   **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the New BSD License which accompanies this    			 **
 **   distribution.											                 **
 **   A copy is found in the textfile LICENSE.txt							 **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Main developers:                                                       **
 **     Juri Strumpflohner		http://blog.js-development.com	             **
 **		Matthias Braunhofer		http://matthias.jimdo.com                    **
 **                                                                          **
 **  **********************************************************************  */

package com.aroundj2me.controller;

import java.util.Vector;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.List;

import com.aroundj2me.location.LocationController;
import com.aroundj2me.main.AboutForm;
import com.aroundj2me.main.AroundJ2ME;
import com.aroundj2me.model.AbstractHTTPCommunicator;
import com.aroundj2me.model.Comment;
import com.aroundj2me.model.Communicator;
import com.aroundj2me.model.Constants;
import com.aroundj2me.model.GMapsGeocoder;
import com.aroundj2me.model.ICredentialProvider;
import com.aroundj2me.model.Place;
import com.aroundj2me.model.ThreadListener;
import com.aroundj2me.utility.AlertCreator;
import com.aroundj2me.utility.ImageLoader;
import com.aroundj2me.utility.ProgressForm;
import com.aroundj2me.view.CategoryListView;
import com.aroundj2me.view.CommentView;
import com.aroundj2me.view.NewPlaceView;
import com.aroundj2me.view.PlaceListView;
import com.aroundj2me.view.PlaceViewer;
import com.aroundj2me.view.SearchView;
import com.aroundj2me.xml.KMLDecoder;
import com.aroundj2me.xml.XMLDecoder;
import com.aroundj2me.xml.XMLEncoder;

//TODO MPS refactor the whole class...responsibility should be layed out to new controllers
public class ApplicationController implements CommandListener, ThreadListener {
	private AroundJ2ME parent = null;
	private Display display = null;
	private List navList = null;
	private AbstractHTTPCommunicator comm = null;
	private XMLEncoder encoder = null;
	private XMLDecoder decoder = null;

	// Controllers
	private ScreenController screenController = null;
	private LocationController locationController = null;
	private ICredentialProvider credentialProvider = null;

	private String type; // indicates the type of request
	private final int MAX_DATA_AMOUNT = 5;
	private int dataStartIndex = 0;
	private String lastRetrieveParam = null;

	// commands
	private Command exitCommand;

	// navigation constants
	private final String NEW_PLACE = "Create Place";
	private final String SEARCH_PLACE = "Search";
	private final String RETRIEVE_BY_CATEGORY = "Places by category";
	private final String ABOUT = "About";

	// remove Simulator when deployed on production environment
	private final String SIMULATOR = "Location Simulator";

	private final String RETRIEVE_MY_PLACE = "my places";
	private final String RETRIEVE_LOCATION_PLACES = "location places";
	private final String RETRIEVE_GLOBAL_PLACES = "global places";

	// Views
	private NewPlaceView newPlace = null;
	private SearchView searchView = null;
	private CategoryListView categoryListView = null;
	private PlaceListView placeList = null;
	private PlaceViewer viewer = null;
	private ProgressForm progressWindow = null;
	private CommentView commentView = null;
	private AboutForm aboutForm = null;

	public ApplicationController(AroundJ2ME parent) {
		this.parent = parent;
		this.display = parent.getDisplay();

		createMainMenu();
		createCommands();
		navList.setCommandListener(this);

		screenController = ScreenController.getInstance();
		screenController.setDisplay(this.display);

		// just for testing with random locations
		locationController = new LocationController(this);

		progressWindow = new ProgressForm(false);

		encoder = new XMLEncoder();
		this.comm = createCommunicator();
	}

	private Communicator createCommunicator() {
		String serverURL = parent.getAppProperty("serverURL");
		if (serverURL == null || serverURL.equals("")) {
			this.display
					.setCurrent(AlertCreator
							.getErrorAlert("URL of Around(J2)ME server not available!"));
		} else {
			return new Communicator(serverURL, progressWindow);
		}
		return null;
	}

	/**
	 * Creates the main menu, basically the navigation list with all its entries
	 * 
	 */
	private void createMainMenu() {
		if (navList == null)
			navList = new List("Around(J2)ME", List.IMPLICIT);
		navList
				.append(NEW_PLACE, ImageLoader
						.getImage(ImageLoader.CREATE_POST));
		navList.append(SEARCH_PLACE, ImageLoader.getImage(ImageLoader.SEARCH));

		// TODO replace image with appropriate one
		navList.append(RETRIEVE_BY_CATEGORY, ImageLoader
				.getImage(ImageLoader.SEARCH));
		navList.append(ABOUT, ImageLoader.getImage(ImageLoader.INFO));
		navList.append(SIMULATOR, ImageLoader.getImage(ImageLoader.GLOBE));

		// navList.append(RETRIEVE_MY_PLACE,
		// ImageLoader.getImage(ImageLoader.MY_POSTS));
		// navList.append(RETRIEVE_LOCATION_PLACES,
		// ImageLoader.getImage(ImageLoader.CELL_POSTS));
		// navList.append(RETRIEVE_GLOBAL_PLACES,
		// ImageLoader.getImage(ImageLoader.GET_POSTS));
	}

	/**
	 * Creates the commands
	 * 
	 */
	private void createCommands() {
		this.exitCommand = new Command("Exit", Command.EXIT, 0);
		this.navList.addCommand(exitCommand);
	}

	/**
	 * Action handler
	 */
	public void commandAction(Command c, Displayable d) {
		if (c == exitCommand) {
			ScreenController.destroyInstance();
			this.display.setCurrent((Displayable) null);
			parent.notifyDestroyed();
		} else if (d == navList) {
			// some navigation item clicked
			if (IsCommandSource(NEW_PLACE)) {
				newPlace = new NewPlaceView("New Place", this);
				screenController.display(newPlace, true);

			} else if (IsCommandSource(SEARCH_PLACE)) {
				dataStartIndex = 0;
				searchView = new SearchView(this);
				screenController.display(searchView, true);

			} else if (IsCommandSource(RETRIEVE_BY_CATEGORY)) {
				// TODO display the list with possible categories
				categoryListView = new CategoryListView(this);
				screenController.display(categoryListView, true);

			} else if (IsCommandSource(RETRIEVE_MY_PLACE)) {
				progressWindow.startProgress("retrieving my places...");
				screenController.display(progressWindow);
				type = Constants.RETRIEVE_PLACE;
				dataStartIndex = 0;
				lastRetrieveParam = Constants.PARAM_RETRIEVE_PERSONAL;
				String xmlString = encoder.encodeRequest(
						Constants.PARAM_RETRIEVE_PERSONAL, dataStartIndex,
						MAX_DATA_AMOUNT, credentialProvider.getUsername(),
						credentialProvider.getUserPassword());
				startWorking(xmlString);

			} else if (IsCommandSource(RETRIEVE_GLOBAL_PLACES)) {
				progressWindow.startProgress("retrieving global places...");
				screenController.display(progressWindow);
				// prepare request
				type = Constants.RETRIEVE_PLACE;
				lastRetrieveParam = Constants.PARAM_RETRIEVE_GLOBAL;
				dataStartIndex = 0;
				String xmlString = encoder.encodeRequest(
						Constants.PARAM_RETRIEVE_GLOBAL, dataStartIndex,
						MAX_DATA_AMOUNT, credentialProvider.getUsername(),
						credentialProvider.getUserPassword());
				startWorking(xmlString);

			} else if (IsCommandSource(RETRIEVE_LOCATION_PLACES)) {
				progressWindow.startProgress("retrieving location places...");
				screenController.display(progressWindow);
				type = Constants.RETRIEVE_PLACE;
				lastRetrieveParam = Constants.PARAM_LOCATION_CELLID;
				dataStartIndex = 0;
				String xmlString = encoder.encodeRequest(
						Constants.PARAM_LOCATION_CELLID, dataStartIndex,
						MAX_DATA_AMOUNT, null, locationController
								.getCurrentLocation(), credentialProvider
								.getUsername(), credentialProvider
								.getUserPassword());
				startWorking(xmlString);

			} else if (IsCommandSource(ABOUT)) {
				aboutForm = new AboutForm();
				screenController.display(aboutForm, true);
			} else if (IsCommandSource(SIMULATOR)) {
				locationController.showView();
			}
		}
	}

	private boolean IsCommandSource(String commandIdentifier) {
		return navList.getString(navList.getSelectedIndex()).equals(
				commandIdentifier);
	}

	/**
	 * Does the necessary setup of the communciation component and creates a new
	 * thread for doing the send/receive work
	 * 
	 * @param xmlString
	 */
	private void startWorking(String xmlString) {
		comm.setRequest(xmlString);
		comm.setThreadListener(this);
		Thread t = new Thread(comm);
		t.start();
	}

	/**
	 * Calls the place-viewer to display a selected place
	 * 
	 * @param p
	 */
	public void displayPlace(Place p) {
		if (viewer == null)
			viewer = new PlaceViewer(this);
		viewer.reset();
		viewer.setPlace(p);
		viewer.displayPlace();
		screenController.display(viewer, true);
	}

	/**
	 * Displays the returned places on the appropriate UI
	 * 
	 */
	public void displayPlaces() {
		if (decoder != null) {
			Vector places = decoder.getPlaces();
			if (places.size() != 0) {
				if (placeList == null)
					placeList = new PlaceListView("Places", this);
				placeList.reset();
				placeList.getPlaceList().setTitle(
						"Places (" + places.size() + ")");
				placeList.setPlaces(places);
				placeList.loadList();
				if (dataStartIndex < MAX_DATA_AMOUNT)
					screenController.display(placeList.getPlaceList(), true);
			} else {
				screenController.display(AlertCreator
						.getInfoAlert("No places have been found!"));
			}
		} else {
			screenController.display(AlertCreator
					.getErrorAlert("Error when displaying places!"));
		}
	}

	/**
	 * Fetches the next places from the server
	 */
	public void fetchNextPlaces() {
		dataStartIndex += MAX_DATA_AMOUNT;
		progressWindow.startProgress("fetching next places...");
		screenController.display(progressWindow);
		type = Constants.RETRIEVE_PLACE;
		// last type
		String xmlString = null;
		if (lastRetrieveParam.equals(Constants.PARAM_RETRIEVE_PERSONAL)
				|| lastRetrieveParam.equals(Constants.PARAM_RETRIEVE_GLOBAL))
			xmlString = encoder.encodeRequest(lastRetrieveParam,
					dataStartIndex, MAX_DATA_AMOUNT, credentialProvider
							.getUsername(), credentialProvider
							.getUserPassword());
		else if (lastRetrieveParam.equals(Constants.PARAM_LOCATION_CELLID))
			xmlString = encoder.encodeRequest(lastRetrieveParam,
					dataStartIndex, MAX_DATA_AMOUNT, null, locationController
							.getCurrentLocation(), credentialProvider
							.getUsername(), credentialProvider
							.getUserPassword());
		else
			executeSearch();
		if (xmlString != null)
			startWorking(xmlString);
	}

	/**
	 * Sends the place to the server
	 * 
	 * @param p
	 */
	public void submitPlace() {
		Place p = new Place();
		if (newPlace.getPlaceId() != 0)
			p.setId(newPlace.getPlaceId());

		String placeName = newPlace.getNameField().getString();
		String placeAddress = newPlace.getAddressField().getString();
		String placeCity = newPlace.getCityField().getString();
		String placePhone = newPlace.getPhoneField().getString();

		if (IsNullOrEmpty(placeName) || IsNullOrEmpty(placeAddress)
				|| IsNullOrEmpty(placeCity)) {
			screenController
					.display(AlertCreator
							.getErrorAlert("All fields have to be filled out correctly!"));
			return;
		}

		p.setName(placeName);
		p.setAddress(placeAddress);
		p.setCity(placeCity);
		p.setPhone(placePhone);

		p.setUser(credentialProvider.getUsername());
		p.setLocation(locationController.getCurrentLocation());

		// if this is an edit operation, effect should be visible on PlaceViewer
		// TODO refactor this
		if (newPlace.getTitle().equals("Edit Place")) {
			viewer.setPlace(p);
			viewer.displayPlace();
			int selectedIndex = placeList.getPlaceList().getSelectedIndex();
			placeList.getPlaces().insertElementAt(p, selectedIndex);
			placeList.getPlaceList().delete(selectedIndex);
			placeList.getPlaceList().insert(selectedIndex, p.getTitle(),
					ImageLoader.getImage(ImageLoader.POST));
		}
		progressWindow.startProgress("sending place...");
		screenController.display(progressWindow);
		type = Constants.COMMIT_PLACE;
		String xmlString = encoder.encodePlaceToXML(p, credentialProvider
				.getUsername(), credentialProvider.getUserPassword());
		startWorking(xmlString);
	}

	// TODO refactor this out into a more suitable place (external class)
	private boolean IsNullOrEmpty(String s) {
		if (s == null)
			return true;

		if (s.equals(""))
			return true;

		return false;
	}

	/**
	 * Loads the Place to be edited into the NewPlaceView form
	 * 
	 */
	public void editPlace(Place p) {
		if (p.getUser().equals(credentialProvider.getUsername())) {
			if (newPlace == null)
				newPlace = new NewPlaceView("Edit Place", this);
			newPlace.loadPlace(p);
			screenController.display(newPlace, true);
		} else
			screenController.display(AlertCreator
					.getErrorAlert("Not authorized to edit this place!"));
	}

	/**
	 * Delete's a user's place
	 * 
	 */
	public void deletePlace() {
		Place p = placeList.getSelectedPlace();
		if (p.getUser().equals(credentialProvider.getUsername())) {
			progressWindow.startProgress("deleting place...");
			screenController.display(progressWindow);
			type = Constants.DELETE_PLACE;
			String xmlString = encoder.encodeDeleteRequest(p,
					credentialProvider.getUsername(), credentialProvider
							.getUserPassword());
			startWorking(xmlString);
		} else
			screenController.display(AlertCreator
					.getErrorAlert("Not authorized to delete this place!"));
	}

	/**
	 * Shows the view for inputting the text of a new comment
	 * 
	 */
	public void showCommentView() {
		commentView = new CommentView(this);
		screenController.display(commentView, true);
	}

	/**
	 * sends the created comment to the server for attaching it to the
	 * appropriate place
	 * 
	 */
	public void submitComment() {
		if (commentView == null)
			commentView = new CommentView(this);
		String commentText = commentView.getCommentField().getString();
		if (commentText != null && !commentText.equals("")) {
			progressWindow.startProgress("submitting comment...");
			screenController.display(progressWindow);

			// process and send to server
			type = Constants.COMMIT_PLACE_COMMENT;
			Comment c = new Comment();
			c.setPlaceId(viewer.getPlace().getId());
			c.setContent(commentText);
			c.setUser(credentialProvider.getUsername());

			viewer.getPlace().addComment(c);

			String xmlString = encoder.encodeCommentToXML(c, credentialProvider
					.getUsername(), credentialProvider.getUserPassword());
			viewer.displayPlace();
			startWorking(xmlString);
		}
	}

	/**
	 * Execute the search
	 * 
	 */
	public void executeSearch() {
		if (searchView != null) {
			String searchQuery = searchView.getSearchField().getString();

			if (!searchQuery.equals("")) {
				if (dataStartIndex < MAX_DATA_AMOUNT) {
					progressWindow.startProgress("searching...");
					screenController.display(progressWindow);
				}

				// prepare request
				type = Constants.PARAM_SEARCH;
				lastRetrieveParam = Constants.PARAM_SEARCH;
				String xmlString = encoder.encodeSearchRequest(
						Constants.PARAM_SEARCH, dataStartIndex,
						MAX_DATA_AMOUNT, searchQuery, locationController
								.getCurrentLocation(), credentialProvider
								.getUsername(), credentialProvider
								.getUserPassword());
				startWorking(xmlString);
			} else {
				screenController.display(AlertCreator
						.getErrorAlert("No search query has been entered!"));
			}
		}
	}

	public void executeCategoryRequest() {
		if (categoryListView != null) {
			String categoryToRetrieve = categoryListView.getSelectedCategory();

			progressWindow.startProgress("retrieving places for "
					+ categoryToRetrieve + "...");
			screenController.display(progressWindow);
			type = Constants.RETRIEVE_PLACE;
			dataStartIndex = 0;

			type = Constants.RETRIEVE_PLACE;
			lastRetrieveParam = Constants.REQPARAM_RETRIEVE_BYCATEGORY;
			String xmlString = encoder.encodeRequest(
					Constants.REQPARAM_RETRIEVE_BYCATEGORY, dataStartIndex,
					MAX_DATA_AMOUNT, categoryToRetrieve, locationController
							.getCurrentLocation(), credentialProvider
							.getUsername(), credentialProvider
							.getUserPassword());
			startWorking(xmlString);
		}
	}

	public void geocodePlace() {
		// get the address + city to be geocoded
		String placeAddress = newPlace.getAddressField().getString();
		String placeCity = newPlace.getCityField().getString();

		// if(IsNullOrEmpty(placeAddress) || IsNullOrEmpty(placeCity)){
		// screenController.display(AlertCreator.getErrorAlert("The address and city of the place have to be indicated to geocode the place!"));
		// return;
		// }

		// TODO add progressListener
		type = Constants.GEOCODE_PLACE;

		comm = new GMapsGeocoder(placeAddress, placeCity);
		comm.setThreadListener(this);
		Thread t = new Thread(comm);
		t.start();
	}

	/**
	 * Thread listener notify method This method is thrown when the thread
	 * finishes its work
	 */
	public void threadFinished() {
		if (type == Constants.RETRIEVE_PLACE) {
			hideProgressBar();
			// load PlaceListView
			String respXML = comm.getResponse();

			decoder = new XMLDecoder();
			decoder.setXmlResponseString(respXML);
			try {
				decoder.parseXML();
				displayPlaces();
			} catch (Exception e) {
				screenController.display(AlertCreator.getErrorAlert(comm
						.getStatusMessage()));
			}

		} else if (type == Constants.COMMIT_PLACE) {
			// place has been sent
			hideProgressBar();
			String xmlResponseString = comm.getResponse();
			decoder = new XMLDecoder();
			decoder.setXmlResponseString(xmlResponseString);
			try {
				decoder.parseXML();
				if (decoder.getType().equals(Constants.ALERT)) {
					screenController.display(decoder.getAlert());
					screenController.lastWithDelay();
				} else {
					// destroy object
					newPlace = null;
				}
			} catch (Exception e) {
				screenController.display(AlertCreator.getErrorAlert(comm
						.getStatusMessage()));
			}

		} else if (type == Constants.DELETE_PLACE) {
			hideProgressBar();
			String xmlResponseString = comm.getResponse();
			decoder = new XMLDecoder();
			decoder.setXmlResponseString(xmlResponseString);
			try {
				decoder.parseXML();
				if (decoder.getType().equals(Constants.ALERT)) {
					screenController.display(decoder.getAlert());
					if (decoder.getAlert().getTitle().equals("Info")) {
						// success: delete place from place-list
						placeList.removeSelectedEntry();
					}
				}
			} catch (Exception e) {
				screenController.display(AlertCreator.getErrorAlert(comm
						.getStatusMessage()));
			}

		} else if (type == Constants.COMMIT_PLACE_COMMENT) {
			hideProgressBar();
			screenController.last();
			String xmlResponseString = comm.getResponse();
			decoder = new XMLDecoder();
			decoder.setXmlResponseString(xmlResponseString);
			try {
				decoder.parseXML();
				if (decoder.getAlert().getTitle().equals(Constants.ALERT_INFO)) {
					viewer.displayPlace();
					screenController.display(decoder.getAlert());
				} else {
					screenController.display(decoder.getAlert());
				}
			} catch (Exception e) {
				screenController.display(AlertCreator.getErrorAlert(comm
						.getStatusMessage()));
			}

		} else if (type == Constants.PARAM_SEARCH) {
			hideProgressBar();
			String respXML = comm.getResponse();

			decoder = new XMLDecoder();
			decoder.setXmlResponseString(respXML);
			try {
				decoder.parseXML();
			} catch (Exception e) {
				screenController.display(AlertCreator.getErrorAlert(comm
						.getStatusMessage()));
			}

			searchView.displayResultNotify(decoder.getPlaces().size());

		} else if (type == Constants.GEOCODE_PLACE) {

			String respXML = comm.getResponse();
			KMLDecoder decoder = new KMLDecoder();
			decoder.setXmlResponseString(respXML);
			try {
				decoder.parse();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		comm.removeThreadListener();
	}

	/**
	 * Hides the progress bar Has to be done separately since the progress bar
	 * is a special case
	 * 
	 */
	private void hideProgressBar() {
		screenController.last();
		progressWindow.stopProgress();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
		}
	}

	/**
	 * shows the initial navigation menu
	 * 
	 */
	public void loadInitialNavigationList() {
		display.setCurrent(navList);
	}

	/*
	 * Getters and Setters
	 */

	public List getNavList() {
		return navList;
	}

	public Display getDisplay() {
		return this.getDisplay();
	}

	public NewPlaceView getNewPlace() {
		return newPlace;
	}

	public void setNewPlace(NewPlaceView newPlace) {
		this.newPlace = newPlace;
	}

	public PlaceViewer getViewer() {
		return viewer;
	}

	public void setViewer(PlaceViewer viewer) {
		this.viewer = viewer;
	}

	public int getDataStartIndex() {
		return dataStartIndex;
	}

	public int getMAX_DATA_AMOUNT() {
		return MAX_DATA_AMOUNT;
	}

	public ICredentialProvider getCredentialProvider() {
		return credentialProvider;
	}

	public void setCredentialProvider(ICredentialProvider credentialProvider) {
		this.credentialProvider = credentialProvider;
	}
}
